﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Microsoft.Data.Caching.AdminApi;
using System.Management.Automation;
using Microsoft.Data.Caching.Commands;

namespace MDCAdminTool
{
    public class CacheHost : PowerShellBase<CacheCluster, MdcCache> 
    {
        #region Fields
        private string              _ServiceName;
        private Collection<string>  _Regions;
        private Collection<MdcCache> _CacheNames;
        private bool                _IsOpen;
        private bool                _IsQuorumHost; 
        private CacheCluster        _Cluster;
        private int                 _CachePort;
        private int                 _ClusterPort;
        private long                _HighWM;
        private long                _LowhWM;
        private long                _Size;
        private HostCacheStats      _HostCacheStats;
        #endregion 

        #region Properties
        public Collection<string> Regions
        {
            get { return _Regions; }
            set { _Regions = value; OnPropertyChanged( "Regions" ); }
        }
        public Collection<MdcCache> CacheNames
        {
            get { return _CacheNames; }
            set { _CacheNames = value; OnPropertyChanged( "CacheNames" ); }
        }
        public bool IsOpen
        {
            get { return _IsOpen; }
            set { _IsOpen = value; OnPropertyChanged( "IsOpen" ); }
        }
        public CacheCluster Cluster
        {
            get { return _Cluster; }
            set { _Cluster = value; OnPropertyChanged( "Cluster" ); }
        }
        public int CachePort
        {
            get { return _CachePort; }
            set { _CachePort = value; OnPropertyChanged( "CachePort" ); }
        }
        public int ClusterPort
        {
            get { return _ClusterPort; }
            set { _ClusterPort = value; OnPropertyChanged( "ClusterPort" ); }
        }
        public long HighWm
        {
            get { return _HighWM; }
            set { _HighWM = value; OnPropertyChanged( "HighWm" ); }
        }
        public long LowhWm
        {
            get { return _LowhWM; }
            set { _LowhWM = value; OnPropertyChanged( "LowhWm" ); }
        }
        public long Size
        {
            get { return _Size; }
            set { _Size = value; OnPropertyChanged( "Size" ); }
        }
        public string ServiceName
        {
            get { return _ServiceName; }
            set { _ServiceName = value; OnPropertyChanged( "ServiceName" ); }
        }
        public bool IsQuorumHost
        {
            get { return _IsQuorumHost; }
            set { _IsQuorumHost = value; OnPropertyChanged( "IsQuorumHost" ); }
        }
        public HostCacheStats HostCacheStats
        {
            get { return _HostCacheStats; }
            set { _HostCacheStats = value; OnPropertyChanged( "HostCacheStats" ); }
        }
        #endregion

        /// <summary>
        /// Cache Host Wrapper.
        /// </summary>
        /// <param name="hostName">The name of the cache host.</param>
        /// <param name="cachePort">The cache port number of the cache host.</param>
        public CacheHost( string hostName , int cachePort )
        {
            Type  = CacheType.CacheHost;
            Name   = hostName;
            CachePort  = cachePort;
            Params     = new Dictionary<string , object>
                        {
                            {"HostName"  , Name},
                            {"CachePort" , CachePort}
                        };

            GetCacheNames();
            LoadConfig();
        }
        public CacheHost( HostInfo hostInfo , CacheCluster cluster ) : this( hostInfo.HostName , hostInfo.PortNo )
        {
            Cluster = cluster;
            Status  = hostInfo.Status;
        }

        #region PowerShell Commands

        /// <summary>
        /// Returns the configuration information about the specified cache host.
        /// </summary>
        /// <returns>Returns the configuration information about the specified cache host.</returns>
        public override void LoadConfig()
        {
            try
            {
                var r = Invoke( "Get-CacheConfig" , Params );
                if ( r == null || r.Count == 0 ) return;
                var props = r[0].Properties;

                CachePort       = (int)props["CachePort"].Value;
                ClusterPort     = (int)props["ClusterPort"].Value;
                HighWm          = (long)props["HighWatermark"].Value;
                Name        = (string)props["HostName"].Value;
                IsQuorumHost    = (bool)props["IsQuorumHost"].Value;
                LowhWm          = (long)props["LowWatermark"].Value;
                ServiceName     = (string)props["ServiceName"].Value;
                Size            = (long)props["Size"].Value;

                UpdateStatus();
            }
            catch ( Exception ex)
            {
                this.AddResultToLog( ex.Message );
            }
        }

        /// <summary>
        /// Updates the cache host configuration to the values specified in the command parameters.
        /// Example: Set-CacheConfig -HostName CacheServer1 -CachePort 22233 -LowWM 80 
        /// </summary>
        /// <param name="clusterPort">The cluster port number that is used by the cache host.</param>
        /// <param name="newClusterPort">The new cache port number.</param>
        /// <param name="cacheSize">The total amount of memory allocated for storing data on the cache host, in units of megabytes (MB).</param>
        /// <param name="highWM">The high watermark percentage of memory, when eviction should begin evicting objects that have not yet expired.</param>
        /// <param name="lowWM">The low watermark percentage of memory, when eviction should begin evicting expired objects. After reaching HWM, when eviction should stop evicting objects that have not yet expired.</param>
        public override void SaveConfig()
        {
            var p = new Dictionary<string, object>
                        {
                            {"HostName" ,  Name},
                            {"CachePort", CachePort},
                            //{"CachePortNumber"  , newCachePort}, TODO://
                            {"ClusterPortNumber", ClusterPort},
                            {"CacheSize", Size},
                            {"HighWM"   , HighWm},
                            {"LowWM"    , LowhWm }
                        };

            Invoke( "Set-CacheConfig" , p );
            UpdateStatus();
        }

        /// <summary>
        /// Starts the specified cache host service. After a cache server reboot, this command is required 
        /// to allow the cache host service to re-join a running cache cluster.
        /// </summary>
        public override void Start()
        {
            Invoke( "Start-CacheHost" , Params );
            UpdateStatus();
        }

        /// <summary>
        /// Stops the specified cache host service.
        /// Note: The Stop-CacheHost command will not stop a cache host Windows service if it is 
        /// performing the cluster management role and stopping the cache host will cause the entire 
        /// cluster to shut down.
        /// </summary>
        public override void Stop()
        {
            Invoke( "Stop-CacheHost" , Params );
            UpdateStatus();
            GetCacheNames();
        }
        public void Restart()
        {
            Stop();
            UpdateStatus();
            Start();
            UpdateStatus();
        }

        /// <summary>
        /// Shows statistics for the specified cache host including: size (in bytes), number of caches, regions, cached objects, and cache misses.
        /// Note: See the next table for information about how to use this command with the CacheName parameter.
        /// </summary>
        public override CacheStats LoadStatistics()
        {
            var r = Invoke( "Get-CacheStatistics" , Params );
            var result = BuildHostCacheStats( r );

            if ( result != null && result.Count > 0 ) 
                HostCacheStats = result[0];

            return HostCacheStats;
            
        }

        public void Add( string name )
        {
            Add( new MdcCache( this , name ) );
        }
        public override void Add( MdcCache cache )
        {
            var result = Invoke( "New-Cache" , InitNewCacheParam( cache ) );
            GetCacheNames();
        }
        public override void Add( CacheEntityBase item )
        {
            if ( item is MdcCache ) Add( item as MdcCache );
        }

        /// <summary>
        /// Creates a new named cache when the cluster is running. 
        /// All values except for CacheName are optional.
        /// </summary>
        /// <param name="name">The name of the cache</param>
        /// <param name="isHighAvailability">A value of true enables the high availability feature. A value of 0 disables the high availability feature. The default value is false.</param>
        /// <param name="eviction"> value of LRU enables Least Recently Used eviction. A value of none disables eviction. The default value is LRU. Warning: a value of none may cause a cache host to run out of memory.</param>
        /// <param name="TTL">The default time-out for objects stored in cache before they expire, in minutes. The default value is 10.</param>
        /// <param name="isExpirable">Use this parameter to disable expiration; otherwise expiration will be enabled at the default settings.</param>
        public void AddCacheName(
            string cacheName ,
            bool isHighAvailability ,
            bool notificationsEnabled ,
            Eviction eviction ,
            int Ttl ,
            bool notExpirable )
        {
            Add( new MdcCache( 
                        this , 
                        cacheName , 
                        notificationsEnabled , 
                        isHighAvailability , 
                        eviction , 
                        Ttl , 
                        notExpirable ) 
                );
            
        }

        /// <summary>
        /// Removes a named cache.
        /// </summary>
        public override void Remove( MdcCache cache )
        {
            Remove( cache as CacheEntityBase );
        }
        public override void Remove( CacheEntityBase item )
        {
            Invoke( "Remove-Cache" , new Dictionary<string , object> { { "CacheName" , item.Name } } );
            LoadChildren();
        }

        internal static Collection<HostCacheStats> BuildHostCacheStats( Collection<PSObject> r )
        {
            var result = new Collection<HostCacheStats>();

            if ( r != null )
                foreach ( PSObject o in r )
                {
                    result.Add( ConvertPSObjectToHostCacheStats( o ) );
                }
            return result;
        }
        internal static HostCacheStats ConvertPSObjectToHostCacheStats( PSObject o )
        {
            return
                new HostCacheStats
                    {
                        ItemCount = (long)o.Properties["ItemCount"].Value ,
                        MissCount = (long)o.Properties["MissCount"].Value ,
                        NamedCacheCount = (long)o.Properties["NamedCacheCount"].Value ,
                        RegionCount = (long)o.Properties["RegionCount"].Value ,
                        RequestCount = (long)o.Properties["RequestCount"].Value ,
                        Size = (long)o.Properties["Size"].Value
                    };
        }
        internal static HostConfig ConvertPSObjectToHostConfig( PSObject o )
        {
            return
                new HostConfig
                {
                    HostName      = (string)o.Properties["HostName"].Value ,
                    HighWatermark = (long)o.Properties["HighWatermark"].Value ,
                    LowWatermark  = (long)o.Properties["LowWatermark"].Value ,
                    IsQuorumHost  = (bool)o.Properties["IsQuorumHost"].Value ,
                    CachePort     = (int)o.Properties["CachePort"].Value ,
                    ClusterPort   = (int)o.Properties["ClusterPort"].Value ,
                    ServiceName   = (string)o.Properties["ServiceName"].Value,
                    Size          = (long)o.Properties["Size"].Value
                };
        }


        /// <summary>
        /// Lists all caches and regions that reside on the specified cache host.
        /// Note: See the next table for information about using this command with no parameters.
        /// </summary>
        public void GetCacheNames()
        {
            var r = Invoke( "Get-Cache" );
            var result = new Collection<MdcCache>();

            if( r == null ) return;

            foreach ( var host in r )
            {
                if ( host.BaseObject is CacheInfo )
                {
                    result.Add( new MdcCache( this , host.BaseObject as CacheInfo ) );

                }
            }
            CacheNames = result;
        }
        public override void LoadChildren()
        {
            GetCacheNames();
        }
        
        #endregion

        void UpdateStatus()
        {
            Cluster.LoadCacheHosts();
            Status = Cluster.Hosts.Where( x => x.Name == Name ).First().Status;
        }

        Dictionary<string , object> InitNewCacheParam( MdcCache cache )
        {
            return new Dictionary<string , object>
                     {
                         {"CacheName"    , cache.Name},
                         //{"CachePort"    , sCachePort},
                         {"Secondaries"  , cache.IsHighAvailability ? 1 : 0 },
                         {"NotificationsEnabled", cache.NotificationsEnabled},
                         {"Eviction"     , cache.Eviction},
                         {"TTL"         , cache.TTL },
                         {"NotExpirable" , cache.NotExpirable},
                     };
        }
    }
}
